package cn.bigcontainer.alt.meta.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import cn.bigcontainer.alt.common.entity.BigMap;
import cn.bigcontainer.alt.common.util.BigConstant;
import cn.bigcontainer.alt.common.util.WoKit;
import cn.bigcontainer.alt.common.util.WoPinyinKit;
import cn.bigcontainer.alt.common.util.WoVelocityKit;
import cn.bigcontainer.alt.meta.dao.MDao;
import cn.bigcontainer.alt.meta.dto.MAppDto;
import cn.bigcontainer.alt.meta.dto.MCrud;
import cn.bigcontainer.alt.meta.dto.MObjectDto;
import cn.bigcontainer.alt.meta.dto.MViewDto;
import cn.bigcontainer.alt.meta.exception.MException;
import cn.bigcontainer.alt.meta.mapper.MAppMapper;
import cn.bigcontainer.alt.meta.mapper.MObjectMapper;
import cn.bigcontainer.alt.meta.mapper.MViewMapper;
import cn.bigcontainer.alt.meta.po.MApp;
import cn.bigcontainer.alt.meta.po.MButton;
import cn.bigcontainer.alt.meta.po.MObject;
import cn.bigcontainer.alt.meta.po.MProperty;
import cn.bigcontainer.alt.meta.po.MRelation;
import cn.bigcontainer.alt.meta.po.MView;
import cn.bigcontainer.alt.meta.po.MViewProperty;
import cn.bigcontainer.alt.meta.service.MetaService;
import cn.bigcontainer.alt.meta.util.MConstant;
import cn.bigcontainer.alt.meta.util.MUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * WOS辅助服务，实现代码、元数据生成及管理。
 *
 * @author cailei
 * @date 2017年6月14日
 */
@Service
@Transactional
@Slf4j
public class MetaServiceImpl implements MetaService {

    private String getDefaultCrudId(String objId) {
        return "crudx_" + objId;
    }

    private MApp createDefaultCrud(MObject obj) {
        MApp crud = new MApp();
        crud.setId(getDefaultCrudId(obj.getId()));
        crud.setIdx(0);
        crud.setName(obj.getName());
        crud.setObjectId(obj.getId());
        crud.setData(new BigMap<String, Object>().bigPut(MCrud.PARAM_CREATE, getDefaultFormViewId(obj.getId(), true))
                .bigPut(MCrud.PARAM_UPDATE, getDefaultFormViewId(obj.getId(), false))
                .bigPut(MCrud.PARAM_LIST, getDefaultListViewId(obj.getId()))
                .bigPut(MCrud.PARAM_SEARCH, getDefaultSearchViewId(obj.getId())));
        crud.setStatus(2);
        crud.setTemplate("meta/crud");
        crud.setType(MAppDto.TYPE_CRUD);
        return crud;
    }

    /**
     * @param viewId   视图ID
     * @param propName 列名称，例如：MNAME
     * @return
     */
    private String getViewPropId(String viewId, String propName) {
        return MUtil.createViewPropertyId(viewId, propName);
    }

    private String getDefaultListViewId(String objId) {
        return "lx_" + objId;
    }

    private MView createDefaultListView(MObject obj) {
        MView list = new MView();
        list.setHeight(320);
        list.setId(getDefaultListViewId(obj.getId()));
        list.setIdx(0);
        list.setName(obj.getName() + "列表");
        list.setObjectId(obj.getId());
        list.setWidth(720);
        list.setType(MViewDto.TYPE_LIST);
        // list.setData(new BigMap<String, Object>().bigPut(MViewDto.PARAM_LIST_ROWS, 10));
        return list;
    }

    private MViewProperty createDefaultListProp(MObject obj, MProperty prop, int i) {
        MViewProperty lp = new MViewProperty();
        String listViewId = getDefaultListViewId(obj.getId());
        lp.setId(getViewPropId(listViewId, prop.getName()));
        lp.setIndex(prop.getIndex());
        lp.setName(prop.getDescription());
        lp.setPropertyId(prop.getId());
        if (i > 4) {
            lp.setType(MViewProperty.TYPE_HIDDEN);
        } else {
            lp.setType(MViewProperty.TYPE_TEXT);
        }
        lp.setWidth(100);
        if (prop == MObjectDto.getNameProperty(obj)) {
            lp.setWidth(-1);
        }
        if (prop.getType().equals(MProperty.TYPE_DATETIME)) {
            if (MConstant.DEFAULT_UPDATE_TIME_SQL.equalsIgnoreCase(prop.getDefaultValue())) {
                lp.setOrderIndex(0);
                lp.setOrderTypeDesc();
            }
            lp.setWidth(160);
        }
        return lp;
    }

    private MViewProperty createDefaultListProp(MObject obj, MRelation relation) {
    	if (relation.is2C()) {
    		return null;
    	}
        MViewProperty lp = new MViewProperty();
        String listViewId = getDefaultListViewId(obj.getId());
        lp.setId(getViewPropId(listViewId, "r" + relation.getIndex()));
        lp.setIndex(relation.getIndex());
        lp.setName(relation.getName());
        lp.setRelationId(relation.getId());
        lp.setPropertyId(relation.getRelatedObject().getNameProperty().getId());
        if (relation.getIndex() == 0) {
            lp.setType(MViewProperty.TYPE_TEXT);
        } else {
            lp.setType(MViewProperty.TYPE_HIDDEN);
        }
        lp.setWidth(100);
        return lp;
    }

    private String createButtonId(String viewId, String buttonType) {
        return viewId + "_" + buttonType;
    }

    /**
     * 列表视图新建按钮
     *
     * @param listId
     * @return
     */
    private MButton createDefaultListCreateButton(String listId) {
        MButton lb = new MButton();
        lb.setIcon("fa fa-plus-circle");
        lb.setId(createButtonId(listId, MButton.TYPE_CREATE));
        lb.setIndex(0);
        lb.setName("新建");
        lb.setDescription(lb.getName());
        lb.setData(new BigMap<String, Object>().bigPut(MButton.PARAM_SEL, MButton.SEL_NONE));
        lb.setType(MButton.TYPE_CREATE);
        return lb;
    }

    /**
     * 列表视图修改按钮
     *
     * @param listId
     * @return
     */
    private MButton createDefaultListUpdateButton(String listId) {
        MButton lb = new MButton();
        lb.setIcon("fa fa-edit");
        lb.setId(createButtonId(listId, MButton.TYPE_UPDATE));
        lb.setIndex(0);
        lb.setName("修改");
        lb.setDescription(lb.getName());
        lb.setData(new BigMap<String, Object>().bigPut(MButton.PARAM_SEL, MButton.SEL_SINGLE));
        lb.setType(MButton.TYPE_UPDATE);
        return lb;
    }

    private String getDefaultFormViewId(String objId, boolean isCreate) {
        if (isCreate) {
            return "cx_" + objId;
        }
        return "ux_" + objId;
    }

    /**
     * 列表视图删除按钮
     *
     * @param listId
     * @return
     */
    private MButton createDefaultListDeleteButton(String listId) {
        MButton lb = new MButton();
        lb.setIcon("fa fa-minus-circle");
        lb.setId(createButtonId(listId, MButton.TYPE_DELETE));
        lb.setIndex(10);
        lb.setName("删除");
        lb.setDescription(lb.getName());
        lb.setSelectType(MButton.SEL_MULTIPLE);
        lb.setType(MButton.TYPE_DELETE);
        return lb;
    }

    /**
     * 创建表单视图数据
     *
     * @param obj
     * @param isCreate 是否新建表单
     * @return
     */
    private MView createDefaultFormView(MObject obj, boolean isCreate) {
        MView form = new MView();
        form.setId(getDefaultFormViewId(obj.getId(), isCreate));
        form.setType(MViewDto.TYPE_FORM);
        form.setIdx(0);
        String desc = "新建";
        if (!isCreate) {
            desc = "修改";
        }
        form.setName(desc + obj.getName());
        form.setObjectId(obj.getId());
        form.setColumns(1);
        form.setWidth(720);
        form.setHeight(600);
        return form;
    }

    /**
     * 设置表单视图属性的类型及相关属性
     *
     * @param prop
     * @param fp
     */
    private void setFormPropType(MProperty prop, MViewProperty fp) {
        fp.setType(MViewProperty.TYPE_TEXT);
        switch (prop.getType()) {
            case MProperty.TYPE_DATE:
                fp.setType(MViewProperty.TYPE_DATE);
                break;
            case MProperty.TYPE_DATETIME:
                fp.setType(MViewProperty.TYPE_DATETIME);
                break;
            case MProperty.TYPE_INTEGER:
            case MProperty.TYPE_SMALLINT:
                fp.setType(MViewProperty.TYPE_NUMBER);
                // Double max = Math.pow(10, prop.getLength()) - 1;
                // Double min = -max;
                fp.setMax(prop.getMax());
                fp.setMin(prop.getMin());
                // 设置数值控件允许的小数位数
                fp.setData(new BigMap<String, Object>().bigPut(MViewProperty.PARAM_SCALE, 0));
                break;
            case MProperty.TYPE_NUMERIC:
                fp.setType(MViewProperty.TYPE_NUMBER);
                // max = Math.pow(10, prop.getPrecision() - prop.getScale()) - 1;
                // min = -max;
                fp.setMax(prop.getMax());
                fp.setMin(prop.getMin());
                // 设置数值控件允许的小数位数
                fp.setData(new BigMap<String, Object>().bigPut(MViewProperty.PARAM_SCALE, prop.getScale()));
                break;
            case MProperty.TYPE_VARCHAR:
                fp.setMin("0");
                fp.setMax(String.valueOf(prop.getLength()));
                if (prop.getLength() < 200) {
                    fp.setType(MViewProperty.TYPE_TEXT);
                } else if (prop.getLength() < 500) {
                    fp.setType(MViewProperty.TYPE_TEXTAREA);
                    fp.setHeight(100);
                } else {
                    fp.setType(MViewProperty.TYPE_TEXTAREA);
                    fp.setHeight(200);
                }
                break;
            case MProperty.TYPE_TEXT:
                fp.setMin("0");
                fp.setMax("99999");
                fp.setType(MViewProperty.TYPE_TEXTAREA);
                fp.setHeight(200);
                break;
        }
    }

    public static void main(String[] args) {
        String word = "[^\\s(,:)]+";
        Matcher matcher = Pattern.compile(String.format("(%s)[:](%s)[,]?", word, word)).matcher("创建人(关联:用户,类型:多对一/继承/多对多,默认值:当前用户)");
        while (matcher.find()) {
            for (int i = 0; i < matcher.groupCount(); i ++) {
                log.warn(matcher.group(i + 1));
            }
        }

        // [创建人(关联:用户,类型:多对一/继承/多对多,默认值:当前用户)]或者[关联:用户,类型:多对一/继承/多对多]
    }

    /**
     * 创建表单属性
     *
     * @param obj
     * @param prop
     * @param isCreate 是否新建表单视图
     * @return
     */
    private MViewProperty createDefaultFormProp(MObject obj, MProperty prop, boolean isCreate) {
        MViewProperty fp = new MViewProperty();
        String formViewId = getDefaultFormViewId(obj.getId(), isCreate);
        fp.setId(getViewPropId(formViewId, prop.getName()));
        fp.setIndex(prop.getIndex());
        fp.setName(prop.getDescription());
        fp.setPropertyId(prop.getId());
        this.setFormPropType(prop, fp);
        fp.setAllowsBlank(1 - prop.getMandatory());
        fp.setColSpan(1);
        fp.setRowSpan(1);
        fp.setSubmit(1);
        fp.setInitValue(prop.getDefaultValue());
        if (isCreate) {
        	fp.setReplaceInit(1);
        }
        fp.setReadOnly((prop.getKey() == 1 && !isCreate) ? 1 : 0);
        if (prop.getType().equals(MProperty.TYPE_DATETIME)) {
            if (isCreate) {
                if (MConstant.DEFAULT_CREATE_TIME_SQL.equalsIgnoreCase(prop.getDefaultValue())
                        || MConstant.DEFAULT_UPDATE_TIME_SQL.equalsIgnoreCase(prop.getDefaultValue())) {
                    // 不提交
                    fp.setSubmit(0);
                    // 不显示
                    fp.setType(MViewProperty.TYPE_HIDDEN);
                } else if (MConstant.DEFAULT_CURRENT_TIME.equals(prop.getDefaultValue())) {
                    fp.setInitValue("${dateKit.now}");
                    fp.setReplaceInit(1);
                    fp.setReadOnly(1);
                }
            } else {
                if (MConstant.DEFAULT_CREATE_TIME_SQL.equalsIgnoreCase(prop.getDefaultValue())
                        || MConstant.DEFAULT_UPDATE_TIME_SQL.equalsIgnoreCase(prop.getDefaultValue())) {
                    // 不提交
                    fp.setSubmit(0);
                    // 不修改
                    fp.setReadOnly(1);
                    fp.setType(MViewProperty.TYPE_TEXT);
                } else if (MConstant.DEFAULT_CURRENT_TIME.equals(prop.getDefaultValue())) {
                    fp.setInitValue("${dateKit.now}");
                    // form加载时，如果值为空，则采用当前时间
                    fp.setReplaceInit(1);
                    fp.setReadOnly(1);
                }
            }
        }
        return fp;
    }

    /**
     * 创建表单属性
     *
     * @param obj
     * @param relation
     * @param isCreate 是否新建表单视图
     * @return
     */
    private MViewProperty createDefaultFormProp(MObject obj, MRelation relation, boolean isCreate) {
        if (relation.is12M()) {
            return null;
        }
        MViewProperty fp = new MViewProperty();
        String formViewId = getDefaultFormViewId(obj.getId(), isCreate);
        fp.setId(getViewPropId(formViewId, "r" + relation.getIndex()));
        fp.setIndex(relation.getIndex());
        fp.setName(relation.getName());
        fp.setRelationId(relation.getId());
        // this.setFormPropType(prop, fp);
        fp.setType(MViewProperty.TYPE_OBJECT_SELECTOR);
        if (relation.is2C()) {
        	fp.setType(MViewProperty.TYPE_OBJECT_INPUTOR);
        }
        fp.setAllowsBlank(0);
        fp.setColSpan(1);
        fp.setRowSpan(1);
        fp.setSubmit(1);
        fp.setReadOnly(0);
        if (isCreate) {
            if (MConstant.DEFAULT_CREATOR.equals(relation.getDefault())) {
                fp.setInitValue("${" + BigConstant.SESSION_USER + ".id}");
                fp.setReplaceInit(1);
                fp.setType(MViewProperty.TYPE_HIDDEN);
                fp.setReadOnly(1);
            } else if (MConstant.DEFAULT_UPDATOR.equals(relation.getDefault())) {
                fp.setInitValue("${" + BigConstant.SESSION_USER + ".id}");
                fp.setReplaceInit(1);
                fp.setType(MViewProperty.TYPE_HIDDEN);
                fp.setReadOnly(1);
            }
        } else {
            if (MConstant.DEFAULT_CREATOR.equals(relation.getDefault())) {
                fp.setReadOnly(1);
            } else if (MConstant.DEFAULT_UPDATOR.equals(relation.getDefault())) {
                fp.setReadOnly(1);
                fp.setSubmitValue("${" + BigConstant.SESSION_USER + ".id}");
                fp.setInitValue("${" + BigConstant.SESSION_USER + ".id}");
                fp.setReplaceInit(1);
                fp.setReplaceSubmit(3);
            }
        }
        // fp.setMINITVALUE(prop.getMDEFAULT());
        return fp;
    }

    /**
     * 表单视图提交按钮
     *
     * @param formId 表单视图ID
     * @return
     */
    private MButton createDefaultWoFormSubmitButton(String formId, boolean create) {
        MButton fb = new MButton();
        fb.setIcon("fa fa-save");
        String type = MButton.TYPE_SUBMIT_UPDATE;
        if (create) {
            type = MButton.TYPE_SUBMIT_CREATE;
        }
        fb.setId(createButtonId(formId, type));
        fb.setIndex(0);
        fb.setName("提交");
        fb.setDescription(fb.getName());
        fb.setType(type);
        return fb;
    }

    /**
     * 表单视图重置按钮
     *
     * @param formId 表单视图ID
     * @return
     */
    private MButton createDefaultWoFormResetButton(String formId) {
        MButton fb = new MButton();
        fb.setIcon("fa fa-undo");
        fb.setId(createButtonId(formId, MButton.TYPE_RESET));
        fb.setIndex(10);
        fb.setName("重置");
        fb.setDescription(fb.getName());
        fb.setType(MButton.TYPE_RESET);
        return fb;
    }

    /**
     * 表单视图取消按钮
     *
     * @param formId 表单视图ID
     * @return
     */
    private MButton createDefaultWoFormCancelButton(String formId) {
        MButton fb = new MButton();
        fb.setIcon("fa fa-times-circle");
        fb.setId(createButtonId(formId, MButton.TYPE_CANCEL));
        fb.setIndex(20);
        fb.setName("取消");
        fb.setDescription(fb.getName());
        fb.setType(MButton.TYPE_CANCEL);
        return fb;
    }

    private String getDefaultSearchViewId(String objId) {
        return "sx_" + objId;
    }

    /**
     * 创建查询视图数据
     *
     * @param obj
     * @return
     */
    private MView createDefaultWoSearchView(MObject obj) {
        MView form = new MView();
        form.setId(getDefaultSearchViewId(obj.getId()));
        form.setIdx(0);
        form.setName("查询" + obj.getName());
        form.setType(MViewDto.TYPE_SEARCH);
        form.setObjectId(obj.getId());
        return form;
    }

    private MViewProperty createDefaultWoSearchProp(MObject obj) {
        MProperty prop = MObjectDto.getNameProperty(obj);
        if (prop == null) {
            return null;
        }
        MViewProperty sp = new MViewProperty();
        String searchViewId = getDefaultSearchViewId(obj.getId());
        sp.setId(getViewPropId(searchViewId, prop.getName()));
        sp.setIndex(0);
        sp.setName(prop.getDescription());
        sp.setPropertyId(prop.getId());
        sp.setType(MViewProperty.TYPE_TEXT);
        sp.setData(new BigMap<String, Object>().bigPut(MViewProperty.PARAM_COMPARE, MViewProperty.COMPARE_LIKE));
        sp.setWidth(200);
        return sp;
    }

    @Resource
    private MObjectMapper objectMapper;

    @Resource
    private MViewMapper viewMapper;

    @Resource
    private MAppMapper appMapper;

    @Override
    public List<MObjectDto> getObjects() {
        List<MObject> objects = objectMapper.selectList(new QueryWrapper<MObject>().orderByAsc("idx"));
        List<MObjectDto> dtos = WoKit.getDtos(MObjectDto.class, objects);
        dtos.forEach(object -> {
        	List<MApp> apps = appMapper.selectList(new QueryWrapper<MApp>().eq("object_id", object.getId()));
        	object.getData().put("apps", WoKit.getDtos(MAppDto.class, apps));
        });
        return dtos;
    }

    private List<MAppDto> setObjectApps(MObjectDto object) {
        List<MApp> apps = appMapper.selectList(new QueryWrapper<MApp>().eq("object_id", object.getId()));
        List dtos = WoKit.getDtos(MAppDto.class, apps);
        object.getData().put("apps", dtos);
        return dtos;
    }

    @Override
    public MObjectDto getObjectWithApps(String objectId) {
        MObjectDto object = new MObjectDto(objectMapper.selectById(objectId));
        setObjectApps(object);
        object.setMetaShow(true);
        object.getProperties().get(0).setMetaShow(true);
        if (object.getRelations().size() > 0) {
            object.getRelations().get(0).setMetaShow(true);
        }
        return object;
    }

    @Override
    public MObjectDto getObjectWithApps(String viewId, String propertyId, String buttonId) {
        MView view = viewMapper.selectById(viewId);
        MObject object = objectMapper.selectById(view.getObjectId());
        MObjectDto dto = new MObjectDto(object);
        List<MAppDto> apps = setObjectApps(dto);
        MViewDto viewDto = null;
        for (MAppDto app : apps) {
            for (MViewDto v : app.getViews()) {
                if (v.getId().equals(viewId)) {
                    viewDto = v;
                }
            }
        }
        if (!WoKit.isEmpty(propertyId)) {
            viewDto.getProperties().stream().forEach(property -> {
                if (property.getId().equals(propertyId)) {
                    property.setMetaShow(true);
                }
            });
        } else if (!WoKit.isEmpty(buttonId)) {
            viewDto.getButtons().stream().forEach(button -> {
                if (button.getId().equals(buttonId)) {
                    button.setMetaShow(true);
                }
            });
        } else {
            viewDto.setMetaShow(true);
        }
        return dto;
    }

    @Override
    public void createDefaultCrud(String table) {
        // 1.获取WoObject元数据（包含属性和关系）
        MObject obj = objectMapper.selectById(table);
        // 2.删除默认crud相关数据
        appMapper.deleteById(getDefaultCrudId(table));
        // 删除默认创建和修改视图数据
        viewMapper.deleteById(getDefaultFormViewId(table, true));
        viewMapper.deleteById(getDefaultFormViewId(table, false));
        // 删除默认列表视图数据
        viewMapper.deleteById(getDefaultListViewId(table));
        // 删除默认查询视图数据
        viewMapper.deleteById(getDefaultSearchViewId(table));
        // 3.创建默认crud相关数据
        // 创建列表视图
        MView list = this.createDefaultListView(obj);
        list.setButtons(new ArrayList<>());
        list.setProperties(new ArrayList<>());
        // 创建“新建”表单视图
        MView create = this.createDefaultFormView(obj, true);
        create.setButtons(new ArrayList<>());
        create.setProperties(new ArrayList<>());
        // 创建“修改”表单视图
        MView update = this.createDefaultFormView(obj, false);
        update.setButtons(new ArrayList<>());
        update.setProperties(new ArrayList<>());
        // 创建查询视图
        MView search = this.createDefaultWoSearchView(obj);
        search.setButtons(new ArrayList<>());
        search.setProperties(new ArrayList<>());
        // 根据属性创建各个视图属性
        List<MProperty> props = obj.getProperties();
        for (int i = 0; i < props.size(); i++) {
            MProperty prop = props.get(i);
            // 如果不是主键，并且是外键，则略过
            if (prop.getKey() != 1) {
                Boolean related = false;
                for (MRelation r : obj.getRelations()) {
                    if (Arrays.asList(r.getPropertyIds().split(",")).contains(prop.getId())) {
                        related = true;
                    }
                }
                if (related) {
                    continue;
                }
            }
            MViewProperty lp = this.createDefaultListProp(obj, prop, i);
            if (lp != null) {
                list.getProperties().add(lp);
            }
            MViewProperty cp = this.createDefaultFormProp(obj, prop, true);
            if (cp != null) {
                create.getProperties().add(cp);
            }
            MViewProperty up = this.createDefaultFormProp(obj, prop, false);
            if (up != null) {
                update.getProperties().add(up);
            }
        }
        // 根据属性创建各个视图属性
        List<MRelation> relations = obj.getRelations();
        for (int i = 0; i < relations.size(); i++) {
            MRelation r = relations.get(i);
        	MViewProperty lp = this.createDefaultListProp(obj, r);
            if (lp != null) {
                list.getProperties().add(lp);
            }
            MViewProperty cp = this.createDefaultFormProp(obj, r, true);
            if (cp != null) {
                create.getProperties().add(cp);
            }
            MViewProperty up = this.createDefaultFormProp(obj, r, false);
            if (up != null) {
                update.getProperties().add(up);
            }
        }
        MViewProperty sp = this.createDefaultWoSearchProp(obj);
        if (sp != null) {
            search.getProperties().add(sp);
        }
        // 创建列表视图按钮
        MButton lb = this.createDefaultListCreateButton(list.getId());
        list.getButtons().add(lb);
        lb = this.createDefaultListUpdateButton(list.getId());
        list.getButtons().add(lb);
        lb = this.createDefaultListDeleteButton(list.getId());
        list.getButtons().add(lb);
        // 创建“新建”表单视图按钮
        MButton cb = this.createDefaultWoFormSubmitButton(create.getId(), true);
        create.getButtons().add(cb);
        cb = this.createDefaultWoFormResetButton(create.getId());
        create.getButtons().add(cb);
        cb = this.createDefaultWoFormCancelButton(create.getId());
        create.getButtons().add(cb);
        // 创建“修改”表单视图按钮
        MButton ub = this.createDefaultWoFormSubmitButton(update.getId(), false);
        update.getButtons().add(ub);
        ub = this.createDefaultWoFormResetButton(update.getId());
        update.getButtons().add(ub);
        ub = this.createDefaultWoFormCancelButton(update.getId());
        update.getButtons().add(ub);
        
        Collections.sort(list.getProperties());
        Collections.sort(list.getButtons());
        viewMapper.insert(list);
        
        Collections.sort(create.getProperties());
        Collections.sort(create.getButtons());
        viewMapper.insert(create);
        
        Collections.sort(update.getProperties());
        Collections.sort(update.getButtons());
        viewMapper.insert(update);
        
        Collections.sort(search.getProperties());
        Collections.sort(search.getButtons());
        viewMapper.insert(search);
        
        MApp crud = this.createDefaultCrud(obj);
        crud.setData(new BigMap<String, Object>().bigPut(MCrud.PARAM_CREATE, create.getId())
                .bigPut(MCrud.PARAM_UPDATE, update.getId())
                .bigPut(MCrud.PARAM_LIST, list.getId())
                .bigPut(MCrud.PARAM_SEARCH, search.getId()));
        appMapper.insert(crud);
    }

    @Value("${big.platform.java}")
    private String dirJava;

    @Resource
    private WoVelocityKit velocityKit;

    @Override
    public void createJavaCode(String object, String pkg, String srcDir, boolean isCreate) {
        Map<String, Object> mod = new HashMap<String, Object>();
        MObjectDto mObject = new MObjectDto(objectMapper.selectById(object));
        mod.put("object", mObject);
        if (WoKit.isEmpty(pkg)) {
            pkg = "cn.bigcontainer.alt";
        }
        if (WoKit.isEmpty(srcDir)) {
            srcDir = dirJava;
        }
        mod.put("package", pkg);
        // pkg对应的源代码目录
        String pkgDir = String.format("%s/%s", srcDir, pkg.replaceAll("[.]", "/"));
        // 生成PO
        velocityKit.mergeToFile("po.vm", pkgDir + "/" + mObject.getModule().toLowerCase() + "/po/" + mObject.getIdCamelCase() + ".java", mod );
        if (isCreate) {
            // 生成Mapper
            velocityKit.mergeToFile("mapper.vm", pkgDir + "/" + mObject.getModule().toLowerCase() + "/mapper/" + mObject.getIdCamelCase() + "Mapper.java", mod );
            // 如果是中间表，则不生成controller/service和dto代码
            if (mObject.getStatus() == 0) {
        		return;
        	}
            // 生成DTO
            velocityKit.mergeToFile("dto.vm", pkgDir + "/" + mObject.getModule().toLowerCase() + "/dto/" + mObject.getIdCamelCase() + "Dto.java", mod );
            // 生成Service
            velocityKit.mergeToFile("service.vm", pkgDir + "/" + mObject.getModule().toLowerCase() + "/service/" + mObject.getIdCamelCase() + "Service.java", mod );
            // 生成ServiceImpl
            velocityKit.mergeToFile("serviceImpl.vm", pkgDir + "/" + mObject.getModule().toLowerCase() + "/service/impl/" + mObject.getIdCamelCase() + "ServiceImpl.java", mod );
            // 生成Controller
            velocityKit.mergeToFile("controller.vm", pkgDir + "/" + mObject.getModule().toLowerCase() + "/controller/" + mObject.getIdCamelCase() + "Controller.java", mod );
        }
    }

    @Override
    public void createJavaCodes(String module, String pkg, String srcDir, boolean isCreate) {
        if (WoKit.isEmpty(module)) {
            module = "sys";
        }
        List<MObject> objects = objectMapper.selectList(new QueryWrapper<MObject>().eq("module", module));
        for (MObject object : objects) {
            createJavaCode(object.getId(), pkg, srcDir, isCreate);
        }
    }

    /**
     * @param id
     * @param oldView
     * @param newView
     * @return
     */
    public String copyViewPropertyOrButtonId(String id, String oldView, String newView) {
        return id.replace(oldView, newView);
    }

    @Override
    public void createEditorByCrud(String appId) {
        String id = copyCrud(appId);
        // 修改MApp
        MApp app = appMapper.selectById(id);
        MCrud crud = new MCrud(app);
        crud.setType(MAppDto.TYPE_LEDITOR);
        crud.setTemplate("meta/" + MAppDto.TYPE_LEDITOR);
        crud.setListTemplate(String.format("meta/%s/list", MAppDto.TYPE_LEDITOR));
        appMapper.updateById(crud);
        // 更新创建视图（删除取消按钮）
        MViewDto createView = crud.getCreateView();
        createView.removeButtonByType(MButton.TYPE_CANCEL);
        createView.getButtons().forEach(btn -> {
            btn.setSelectType(MButton.SEL_SINGLE);
        });
        viewMapper.updateById(createView);
        // 更新修改视图（删除取消按钮、添加删除按钮）
        MViewDto updateView = crud.getUpdateView();
        updateView.removeButtonByType(MButton.TYPE_CANCEL);
        updateView.getButtons().forEach(btn -> {
            btn.setSelectType(MButton.SEL_SINGLE);
        });
        MButton button = createDefaultListDeleteButton(updateView.getId());
        button.setIndex(20);
        button.setSelectType(MButton.SEL_SINGLE);
        updateView.getButtons().add(button);
        viewMapper.updateById(updateView);
    }

    private static int getMaxNumber(String prefix, List list) {
        int num = -1;
        for (Object o : list) {
            try {
                String id = (String) o.getClass().getMethod("getId").invoke(o);
                String[] strs = id.split("_");
                if (strs[0].startsWith(prefix) && strs[0].length() > prefix.length()) {
                    int n = WoKit.getInt(strs[0].substring(prefix.length()), num);
                    if (n > num) {
                        num = n;
                    }
                }
            } catch (Exception e) {
                log.warn(e.getMessage());
            }
        }
        num++;
        return num;
    }

    public String copyViewId (MView srcView) {
        List<MView> views = viewMapper.selectList(new QueryWrapper<MView>().
                eq("object_id", srcView.getObjectId()).
                eq("type", srcView.getType()));
        String prefix = srcView.getId().substring(0, 1);
        int num = getMaxNumber(prefix, views);
        String viewId = prefix + num + "_" + srcView.getObjectId();
        return viewId;
    }

    public MView cloneView(MView srcView) {
        String viewId = copyViewId(srcView);
        srcView = WoKit.cloneBySetter(srcView, MView.class);
        // 列表属性
        List<MViewProperty> props = srcView.getProperties();
        for (MViewProperty prop : props) {
            prop.setId(this.copyViewPropertyOrButtonId(prop.getId(), srcView.getId(), viewId));
        }
        // 列表按钮
        List<MButton> btns = srcView.getButtons();
        for (MButton btn : btns) {
            btn.setId(this.copyViewPropertyOrButtonId(btn.getId(), srcView.getId(), viewId));
        }
        srcView.setId(viewId);
        return srcView;
    }

    private void copyCrudView(MApp crud, String viewType) {
        MView view = viewMapper.selectById(crud.getData().get(viewType).toString());
        view = cloneView(view);
        viewMapper.insert(view);
        crud.getData().put(viewType, view.getId());
    }

    /*
     * (non-Javadoc)
     *
     * @see wos.web.tools.services.IWoAssistService#copyCrud(java.lang.String)
     */
    @Override
    public String copyCrud(String appId) {
        MApp crud = appMapper.selectById(appId);
        if (!MAppDto.TYPE_CRUD.equals(crud.getType())) {
        	throw new MException (MException.APP_TYPE, crud.getType());
        }
        List<MApp> apps = appMapper.selectList(new QueryWrapper<MApp>().eq("object_id", crud.getObjectId()).eq("type", MAppDto.TYPE_CRUD));
        // 获取crud的ID中前缀的最大数字：crud6_mapp，num=6
        String prefix = "crud";
        int num = getMaxNumber(prefix, apps);
        String crudId = prefix + num + "_" + crud.getObjectId();
        // “列表”视图
        copyCrudView(crud, MCrud.PARAM_LIST);
        // “新建”视图
        copyCrudView(crud, MCrud.PARAM_CREATE);
        // “修改”视图
        copyCrudView(crud, MCrud.PARAM_UPDATE);
        // “查询”视图
        copyCrudView(crud, MCrud.PARAM_SEARCH);
        // CRUD
        crud.setId(crudId);
        crud.setStatus(1);
        crud.setIdx(num * 10);
        crud.setName(crud.getName() + num);
        appMapper.insert(crud);
        return crud.getId();
    }

    private boolean isViewUsed(MApp crud, String vwId) {
        List<MApp> apps = appMapper.selectList(new QueryWrapper<MApp>().eq("object_id", crud.getObjectId()).notIn("id", crud.getId()));
        for (MApp app : apps) {
            for (String key : app.getData().keySet()) {
                if (key.startsWith(MAppDto.PREFIX_VIEW)) {
                    String viewId = (String) app.getData().get(key);
                    if (viewId.equals(vwId)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private void deleteView(MApp crud, String viewType) {
        String viewId = (String) crud.getData().get(viewType);
        if (!isViewUsed(crud, viewId)) {
            viewMapper.deleteById(viewId);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see wos.web.tools.services.IWoAssistService#deleteCrud(java.lang.String)
     */
    @Override
    public void deleteApp(String appId) {
        MApp app = appMapper.selectById(appId);
        if (app == null) {
            log.warn("App[id={}] is not found.", appId);
            return;
        }
        for (String key : app.getData().keySet()) {
            if (key.startsWith(MAppDto.PREFIX_VIEW)) {
                deleteView(app, (String) app.getData().get(key));
            }
        }
        //
        appMapper.deleteById(appId);
    }

    private Map<String, String> getPropMap(String[] info) {
        Map<String, String> propMap = new HashMap<String, String>();
        for (int i = 1; i < info.length; i++) {
            String[] keyVal = info[i].split(":");
            propMap.put(keyVal[0], keyVal[1]);
        }
        return propMap;
    }

    /**
     * @param objectId
     * @param propDesc 格式如方括号中的内容所示： [名称(类型:文本,长度:200,默认值:当前用户名,允许为空:是,主键:否)]
     *                 或者[数量(类型:数字,范围:1/200,小数位:2,默认值:100,允许为空:否)]
     * @return
     */
    private MProperty createProp(String objectId, String propDesc, int index) {
        MProperty p = new MProperty();
        String[] info = propDesc.split("\\(|,|\\)");
        p.setDescription(info[0]);
        p.setName("m" + WoPinyinKit.getPinyinFirstCharLower(info[0]));
        p.setId(objectId + "_" + p.getName());
        p.setIndex(index * 10);
        Map<String, String> meta = this.getPropMap(info);
        if (meta.get("索引") != null) {
        	p.setIndex(WoKit.getInt(meta.get("索引")));
        }
        // 类型
        String cnType = meta.get("类型");
        if (cnType == null) {
            cnType = "文本";
        }
        // 数据长度
        String len = meta.get("长度");
        // 数据长度
        String defaultVal = meta.get("默认值");
        p.setDefaultValue(defaultVal);
        // 允许为空
        String allowNull = meta.get("允许为空");
        if (WoKit.isEmpty(allowNull) || allowNull.equals("是")) {
            p.setMandatory(0);
        } else {
            p.setMandatory(1);
        }
        // 主键
        String key = meta.get("主键");
        if (WoKit.isEmpty(key) || key.equals("否")) {
            p.setKey(0);
        } else {
            p.setKey(1);
        }
        // 范围
        String range = meta.get("范围");
        String max = null;
        String min = null;
        if (!WoKit.isEmpty(range)) {
            String[] r = range.split("/");
            max = r[1];
            min = r[0];
        }
        switch (cnType) {
            case "数字":
                p.setType(MProperty.TYPE_NUMERIC);
                // 小数位
                int iDot = WoKit.getInt(meta.get("小数位"), 2);
                int iLen = WoKit.getInt(len, 10);
                Double dMax = WoKit.getDouble(max, Math.pow(10, iLen) - 1);
                Double dMin = WoKit.getDouble(min, -(Math.pow(10, iLen) - 1));
                iLen = Math.max(iLen, Math.max(WoKit.getDoubleLength(dMax), WoKit.getDoubleLength(dMin)));
                p.setTypeDetail((iLen + iDot) + "," + iDot);
                p.setMax(dMax.toString());
                p.setMin(dMin.toString());
                break;
            case "整数":
                iLen = 10;
                int iDefaultMax = Double.valueOf(Math.pow(10, iLen) - 1).intValue();
                int iMax = WoKit.getInt(max, iDefaultMax);
                int iMin = WoKit.getInt(min, -iDefaultMax);
                p.setType(MProperty.TYPE_INTEGER);
                p.setMax(String.valueOf(iMax));
                p.setMin(String.valueOf(iMin));
                break;
            case "小整数":
                iLen = 4;
                iDefaultMax = Double.valueOf(Math.pow(10, iLen) - 1).intValue();
                iMax = WoKit.getInt(max, iDefaultMax);
                iMin = WoKit.getInt(min, -iDefaultMax);
                p.setMax(String.valueOf(iMax));
                p.setMin(String.valueOf(iMin));
                p.setType(MProperty.TYPE_SMALLINT);
                break;
            case "日期":
                p.setType(MProperty.TYPE_DATE);
                break;
            case "日期时间":
                p.setType(MProperty.TYPE_DATETIME);
                if (MConstant.DEFAULT_CREATE_TIME.equals(defaultVal)) {
                    p.setDefaultValue(MConstant.DEFAULT_CREATE_TIME_SQL);
                } else if (MConstant.DEFAULT_UPDATE_TIME.equals(defaultVal)) {
                    p.setDefaultValue(MConstant.DEFAULT_UPDATE_TIME_SQL);
                }
                break;
            case "大文本":
                p.setType(MProperty.TYPE_TEXT);
                p.setMax(String.valueOf(4000));
                break;
            default:
                p.setType(MProperty.TYPE_VARCHAR);
                iLen = WoKit.getInt(len, 100);
                iMax = WoKit.getInt(max, iLen);
                iMin = WoKit.getInt(min, p.getMandatory());
                iLen = Math.max(iMax, iLen);
                p.setTypeDetail("" + iLen);
                p.setMax(String.valueOf(iMax));
                p.setMin(String.valueOf(iMin));
                break;
        }
        return p;
    }

    /**
     * 
     * @param obj
     * @param rObj
     * @param rName
     * @return
     */
    private MObject createRelationObject(MObject obj, MObject rObj, String rName, Boolean rObjectId) {
        MObject bridge = new MObject();
        String rNamePinyin = WoPinyinKit.getPinyinFirstCharLower(rName);
        bridge.setId(obj.getId() + "_" + rNamePinyin);
        MObject objectLast = objectMapper.selectOne(new QueryWrapper<MObject>().orderByDesc("idx").last("limit 1"));
        Integer max = objectLast == null ? 0 : objectLast.getIdx();
        bridge.setIdx(max + 1);
        bridge.setName(obj.getName() + '-' + rName);
        bridge.setModule(obj.getModule());
        bridge.setDescription(bridge.getName());
        bridge.setNo(obj.getNo() + "_" + rNamePinyin);
        bridge.setStatus(0);
        List<MProperty> props = new ArrayList<MProperty>();
        bridge.setProperties(props);
        // 设置关系对象的属性：关联本方对象
        List<MProperty> keys = MObjectDto.getKeyProperties(obj);
        String ids = "";
        for (int i = 0; i < keys.size(); i++) {
            MProperty k = keys.get(i);
            MProperty p = k.clone();
            p.setKey(0);
            String name = "m" + obj.getNo() + i + "_id";
            p.setDescription("关联" + p.getId());
            p.setId(bridge.getId() + "_" + name);
            p.setName(name);
            props.add(p);
            if (i > 0) {
                ids += ",";
            }
            ids += p.getId();
        }
        BigMap<String, Object> map = new BigMap<>();
        map.put("ids", ids);
        bridge.setData(map);
        // 设置关系对象的属性：关联它方对象
        keys = MObjectDto.getKeyProperties(rObj);
        String rIds = "";
        for (int i = 0; i < keys.size(); i++) {
            MProperty k = keys.get(i);
            MProperty p = k.clone();
            p.setKey(0);
            String name = "inverse_property" + i + "_id";
            p.setDescription("关联它方对象" + rObj.getId() + "等");
            p.setId(bridge.getId() + "_" + name);
            p.setName(name);
            props.add(p);
            if (i > 0) {
                rIds += ",";
            }
            rIds += p.getId();
        }
        map.put("rIds", rIds);
        if (rObjectId) {// 如果M2CM，还需要在关系对象上增加属性，用于存储它方对象id
        	MProperty p = new MProperty();
        	p.setName("related_object_id");
        	p.setId(bridge.getId() + "_" + p.getName());
        	p.setDescription("关联对象id，值包括" + rObj.getId() + "等");
        	p.setType(MProperty.TYPE_VARCHAR);
        	p.setTypeDetail("100");
        	p.setKey(0);
        	p.setMandatory(1);
        	p.setIndex(9999);
        	props.add(p);
        	map.put("rObjectId", p.getId());
        }
        return bridge;
    }

    private Map<String, String> parseRelationData (String relationDesc) {
        Matcher matcher = Pattern.compile(MConstant.REG_KEY_VALUE).matcher(relationDesc);
        Map<String, String> data = new HashMap<>();
        while (matcher.find()) {
            for (int i = 0; i < matcher.groupCount(); i ++) {
                data.put(matcher.group(1), matcher.group(2));
            }
        }
        int index = relationDesc.indexOf('(');
        if (index > 0) {
            data.put("name", relationDesc.substring(0, index));
        } else {
            data.put("name", data.get("关联"));
        }
        if (data.get("类型") == null) {
            data.put("类型", "多对一");
        }
        return data;
    }

    /**
     * @param obj
     * @param relationDesc 格式如方括号中的内容所示：[创建人(关联:用户,类型:多对一/继承/多对多,默认值:当前用户)]或者[关联:用户]
     * 		或者[收件人(关联:用户|用户组,类型:M2CM/M2C1,索引:1)]
     * @return
     */
    private List createRelation(MObject obj, String relationDesc, int index) {
        List list = new ArrayList();
        Map<String, String> relationData = parseRelationData(relationDesc);
        if (relationData.size() < 3) {
            throw new MException(MException.ERR_RELATION_FORMAT, relationDesc);
        }
        MRelation r = new MRelation();
        r.setIndex(index * 10);
        list.add(r);
        // r.setObject(obj);
        // 设置ID
        String prefix = "r_" + obj.getId() + "_";
        r.setId(prefix + index);
        obj.getData().put(MObjectDto.PARAM_RELATION_NUM, index);
        // 关联对象名称 从 - 关联：XXX
        String[] relatedNames = relationData.get("关联").split("[|]");
        // 查找关联对象
        List<MObject> relatedObjs = new ArrayList<MObject>();
        for (String relatedName : relatedNames) {
        	MObject relatedObj = null;
        	if (Arrays.asList(obj.getId(), obj.getName(), obj.getNo()).contains(relatedName)) {
        		relatedObj = obj;
        	} else {
        		relatedObj = objectMapper.selectOne(new QueryWrapper<MObject>().eq("id", relatedName).or().eq("no", relatedName).or().eq("name", relatedName));
                if (relatedObj == null) {
                    relatedObj = this.createMeta(obj.getModule(), relatedName, MConstant.PROPS_DEFAULT);
                }
        	}
            relatedObjs.add(relatedObj);
        }
        // 关系名称
        r.setName(relationData.get("name"));
        r.setRelatedName(obj.getName());
        r.setDefault(relationData.get("默认值"));
        String type = relationData.get("类型");
        switch (type) {
            case "多对一":
            case "M21":
            	r.setType(MRelation.TYPE_M21);
                setRelation(obj, index, list, r, relatedObjs.get(0));
                break;
            case "M2C1":
            	r.setType(MRelation.TYPE_M21);
            	relatedObjs.forEach(relatedObj -> setRelation(obj, index, list, r, relatedObj));
            	// 创建外键关联表的字段
            	MProperty p = new MProperty();
			    p.setName("related_object_id");
			    p.setId(obj.getId() + "_" + p.getName());
			    p.setDescription(r.getName() + "ID");
			    p.setIndex(9999);
			    p.setKey(0);
			    p.setMandatory(0);
			    p.setMax("100");
			    p.setMin("2");
			    p.setType(MProperty.TYPE_VARCHAR);
			    p.setTypeDetail("100");
			    p.setUnique(0);
			    list.add(p);
			    r.setPropertyIdsObjectId(p.getId());
                break;
            case "继承":
            case "M1":
                r.setType(MRelation.TYPE_M1);
                setRelation(obj, index, list, r, relatedObjs.get(0));
                break;
            case "多对多":
            case "M2M":
                r.setType(MRelation.TYPE_M2M);
                r.setPropertyIds(MObjectDto.getKeyIds(obj));
                r.setRelatedObjectId(relatedObjs.get(0).getId());
                r.setRelatedIds(MObjectDto.getKeyIds(relatedObjs.get(0)));
                // 创建关系表元数据
                MObject b = this.createRelationObject(obj, relatedObjs.get(0), r.getName(), false);
                list.add(b);
                r.setRelationObjectId(b.getId());
                r.setRelationIds(b.getData().get("ids").toString());
                r.setRelationInverseIds(b.getData().get("rIds").toString());
                break;
            case "M2CM":
                r.setType(MRelation.TYPE_M2CM);
                r.setPropertyIds(MObjectDto.getKeyIds(obj));
                for (MObject relatedObj : relatedObjs) {
                	if (WoKit.isEmpty(r.getRelatedObjectId())) {
                		r.setRelatedObjectId(relatedObj.getId());
                		r.setRelatedIds(MObjectDto.getKeyIds(relatedObj));
                	} else {
                		r.setRelatedObjectId(r.getRelatedObjectId() + "," + relatedObj.getId());
                		r.setRelatedIds(r.getRelatedIds() + ",," + MObjectDto.getKeyIds(relatedObj));
                	}
                }
                // 创建关系表元数据
                MObject b2 = this.createRelationObject(obj, relatedObjs.get(0), r.getName(), true);
                list.add(b2);
                r.setRelationObjectId(b2.getId());
                r.setRelationIds(b2.getData().get("ids").toString());
                r.setRelationInverseIds(b2.getData().get("rIds").toString());
                r.setRelationRelatedObjectId(b2.getData().get("rObjectId").toString());
                break;
            default:
                throw new MException(MException.ERR_RELATION_TYPE, type);
        }
        return list;
    }

	private void setRelation(MObject obj, int index, List list, MRelation r, MObject relatedObj) {
		List<MProperty> keys = MObjectDto.getKeyProperties(relatedObj);
		if (WoKit.isEmpty(r.getRelatedObjectId())) {
			r.setRelatedObjectId(relatedObj.getId());
			String ids = "";
			for (int i = 0; i < keys.size(); i++) {
			    MProperty key = keys.get(i);
			    MProperty p = new MProperty();
			    p.setName("m" + WoPinyinKit.getPinyinFirstCharLower(r.getName()) + i + "_id");
			    p.setId(obj.getId() + "_" + p.getName());
			    p.setDescription(r.getName() + "ID");
			    p.setIndex(index * 10 + i);
			    p.setKey(0);
			    p.setMandatory(0);
			    p.setMax(key.getMax());
			    p.setMin(key.getMin());
			    p.setType(key.getType());
			    p.setTypeDetail(key.getTypeDetail());
			    p.setUnique(key.getUnique());
			    if (!ids.equals("")) {
			        ids += ",";
			    }
			    list.add(p);
			    ids += p.getId();
			}
			r.setPropertyIds(ids);
			r.setRelatedIds(MUtil.getIds(keys));
		} else {
			r.setRelatedObjectId(r.getRelatedObjectId() + "," + relatedObj.getId());
			// PropertyIds本身有可能有多个逗号隔开，所以此处必须使用双逗号
			// r.setPropertyIds(r.getPropertyIds() + ",," + ids);
			r.setRelatedIds(r.getRelatedIds() + ",," + MUtil.getIds(keys));
		}
	}

    @Resource
    private MDao woDao;

    /**
     * @param module    对象所属的模块
     * @param objName   对象中英文名称
     * @param propsDesc 属性和关系描述，以分号隔开；每个属性的格式如方括号中的内容所示：[名称(类型:文本,长度:200,默认值:
     *                  xxx,允许为空:是,主键:否)]或者[数量(类型:数字,范围:1/200,小数位:2,默认值:100,允许为空:否)]
     *                  ；关系的格式如方括号中的内容所示：[创建人(关联:用户,类型:多对一/继承/多对多)]或者[关联:用户,类型:多对一/继承/
     *                  多对多]；属性和关系描述中的符号中英文都支持。
     * @return
     */
    public MObject createMeta(String module, String objName, String propsDesc) {
    	if (WoKit.isEmpty(propsDesc)) {
    		propsDesc = MConstant.PROPS_DEFAULT;
    	}
        String namePinyin = WoPinyinKit.getPinyinLower(objName);
        // 创建MObject元数据
        MObject obj = new MObject();
        obj.setId(module + "_" + namePinyin);
        // 删除已经存在的对象元数据及表
        this.deleteObject(obj.getId());
        obj.setName(objName);
        MObject objectMax = objectMapper.selectOne(new QueryWrapper<MObject>().orderByDesc("idx").last("limit 1"));
        if (objectMax == null) {
            obj.setIdx(0);
        } else {
            obj.setIdx(objectMax.getIdx() + 1);
        }
        obj.setDescription(objName);
        obj.setModule(module);
        obj.setNo(namePinyin);
        obj.setStatus(1);
        List<MProperty> props = new ArrayList<MProperty>();
        List<MRelation> relations = new ArrayList<MRelation>();
        List<MObject> bridges = new ArrayList<MObject>();
        obj.setProperties(props);
        obj.setRelations(relations);
        // 创建属性
        String[] propsArray = propsDesc.split(";");
        for (int i = 0; i < propsArray.length; i++) {
            if (propsArray[i].indexOf("关联:") < 0) {
                MProperty p = createProp(obj.getId(), propsArray[i], i);
                // 如果p的id和props中的属性id重复，则不加入到props中
                if (props.stream().filter( e-> p.getId().equals(e.getId())).findFirst().isPresent()) {
                    continue;
                }
                props.add(p);
            }
        }
        // 如果主键未设置，则设置第一个属性为主键
        boolean hasKey = false;
        for (MProperty p : props) {
            if (p.getKey() == 1) {
                hasKey = true;
            }
        }
        if (!hasKey) {
            props.get(0).setKey(1);
        }
        // 创建关系
        for (int i = 0; i < propsArray.length; i++) {
            if (propsArray[i].indexOf("关联:") >= 0) {
                // 创建关系时，有可能会创建新的属性
                List list = this.createRelation(obj, propsArray[i], i);
                for (Object o : list) {
                    if (o instanceof MProperty) {
                        props.add((MProperty) o);
                    } else if (o instanceof MRelation) {
                        relations.add((MRelation) o);
                    } else if (o instanceof MObject) {
                        bridges.add((MObject) o);
                    }
                }
            }
        }
        // 排序
        Collections.sort(obj.getProperties());
        Collections.sort(obj.getRelations());
        // 元数据入库
        objectMapper.insert(obj);
        // 创建默认crud
        this.createDefaultCrud(obj.getId());
        // 创建关系表元数据
        for (MObject b : bridges) {
            // 如果表已经存在，则删除其元数据
            this.deleteObjectMeta(b.getId());
            // 插入关系表元数据
            objectMapper.insert(b);
        }
        // 创建表
        // TODO 此处执行之后，事务截止
        woDao.update(MObjectDto.getCreateSQL(obj));
        // 创建关系表
        for (MObject b : bridges) {
            // 如果表已经存在，则删除其元数据及表
            this.dropTable(b.getId());
            // 创建关系表
            // TODO 此处执行之后，事务截止
            woDao.update(MObjectDto.getCreateSQL(b));
        }
        return obj;
    }

    private void deleteObjectMeta(String objId) {
        // 查询视图
        viewMapper.delete(new QueryWrapper<MView>().eq("object_id", objId));
        // 删除应用
        appMapper.delete(new QueryWrapper<MApp>().eq("object_id", objId));
        // 删除表
        objectMapper.deleteById(objId);
    }

    private void dropTable(String objId) {
        // TODO 此处执行之后，事务截止
        woDao.update(MObjectDto.getDropSQL(objId));
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * wos.web.tools.services.IWoAssistService#deleteObject(java.lang.String)
     */
    public void deleteObject(String objId) {
        // 删除元数据
        deleteObjectMeta(objId);
        // drop表
        // TODO 此处执行之后，事务截止
        dropTable(objId);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * wos.web.tools.services.IWoAssistService#confirmCreateSnapShoot(java.lang.
     * String, java.lang.String)
     */
    public boolean confirmCreateSnapShoot(String module, String objName) {
        String objId = module + "_" + WoPinyinKit.getPinyinLower(objName);
        MObject obj = objectMapper.selectById(objId);
        return obj != null;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * wos.web.tools.services.IWoAssistService#deleteViewPropOrButton(java.lang.
     * String, java.lang.String)
     */
    public void deleteViewPropOrButton(String id, String table) {
        String sql = String.format("delete from %s where m_id = '%s'", table, id);
        woDao.delete(sql);
    }

    /**
     * @param appId
     */
    public void updateCrud(String appId) {
        MApp app = appMapper.selectById(appId);
        this.deleteApp(appId);
        if (appId.startsWith("crudx_")) {
            this.createDefaultCrud(app.getObjectId());
        } else {
            this.copyCrud("crudx_" + app.getObjectId());
        }
    }
    
    private void addProperty (MObject object, MProperty property) {
    	List<MProperty> properties = object.getProperties();
		properties.add(property);
		Collections.sort(properties);
		this.addColumn(object, property);
    }
    
    private void addRelation (MObject object, MRelation relation) {
    	List<MRelation> relations = object.getRelations();
		relations.add(relation);
		Collections.sort(relations);
    }

    private void addColumn (MObject object, MProperty p) {
    	String sql = String.format("ALTER TABLE %s ADD %s", object.getId(), p.getSqlFieldClause(object));
    	woDao.update(sql);
    }
    
	@Override
	public MProperty createPropertyMeta(String objectId, String propName, String propDetail) {
		MProperty property = createProp(objectId, String.format("%s(%s)", propName, propDetail), 0);
		MObject object = this.objectMapper.selectById(objectId);
		this.addProperty(object, property);
		this.objectMapper.updateById(object);
		return property;
	}

	@Override
	public List createRelationMeta(String objectId, String propName, String relationDetail) {
		String relationDesc = relationDetail;
		if (!WoKit.isEmpty(propName)) {
			relationDesc = String.format("%s(%s)", propName, relationDetail);
		}
		MObject object = this.objectMapper.selectById(objectId);
		List list = this.createRelation(object, relationDesc, MObjectDto.getRelationMaxIdIndex(object) + 1);
		List<MObject> relationTables = new ArrayList<>();
		for (Object o : list) {
            if (o instanceof MProperty) {
            	MProperty p = (MProperty) o;
            	this.addProperty(object, (MProperty) o);
            } else if (o instanceof MRelation) {
                this.addRelation(object, (MRelation) o);
            } else if (o instanceof MObject) {
				MObject relationTable = (MObject) o;
				this.objectMapper.insert(relationTable);
				// 如果表已经存在，则删除其元数据及表
	            this.dropTable(relationTable.getId());
	            // 创建关系表
	            // TODO 此处执行之后，事务截止
	            woDao.update(MObjectDto.getCreateSQL(relationTable));
            }
        }
		this.objectMapper.updateById(object);
		return list;
	}
}
